package tetris.agent;

import tetris.simulator.State;

public class Agent implements Comparable<Agent>
{

        public enum feature
        {
                OVERHANG, LINES_CLEARED, MAX_HEIGHT, MIN_HEIGHT, NUM_SQUARES, DIFFS, ISOLATION, HOLES
        };

        public double theta[] = new double[feature.values().length];
        public double lines = 0;
        
        public double score;
        // example random agent
        State next_s = new State();
        State test_s = new State();
        State prev_s = new State();
        public Agent()
        {
                // Hand tuned
        		/*
                theta[feature.OVERHANG.ordinal()] = 1.0;
                theta[feature.LINES_CLEARED.ordinal()] = 0.0;
                theta[feature.MAX_HEIGHT.ordinal()] = 0.0;
                theta[feature.MIN_HEIGHT.ordinal()] = 0.0;
                theta[feature.NUM_SQUARES.ordinal()] = 0.0;
                theta[feature.DIFFS.ordinal()] = 0.05;
                theta[feature.ISOLATION.ordinal()] = 0.5;
                theta[feature.HOLES.ordinal()] = 0.9;
                */
                
                // Evolved greedy policy 100 generations
                
                //theta[feature.OVERHANG.ordinal()] =  56.554172;
                //theta[feature.SUM_HEIGHT.ordinal()] = 28.902378;
                //theta[feature.MAX_HEIGHT.ordinal()] = -4.770780;
                //theta[feature.MIN_HEIGHT.ordinal()] = -8.720231;
                //theta[feature.NUM_SQUARES.ordinal()] = -27.046634;
                //theta[feature.DIFFS.ordinal()] = 15.984916;
                //theta[feature.ISOLATION.ordinal()] = 13.933532;
                //theta[feature.HOLES.ordinal()] = 48.962873;
                
                
                // CMA 100 generations
				theta[feature.OVERHANG.ordinal()] =102.484270 ;
				theta[feature.LINES_CLEARED.ordinal()] =- 15.799548;
				theta[feature.MAX_HEIGHT.ordinal()] = -1.962944;
				theta[feature.MIN_HEIGHT.ordinal()] = -2.279447;
				theta[feature.NUM_SQUARES.ordinal()] = 3.409913;
				theta[feature.DIFFS.ordinal()] =  12.931093;
				theta[feature.ISOLATION.ordinal()] = 24.722818;
				theta[feature.HOLES.ordinal()] = 19.323345;
            
                
        }

        public State forwardSimulate(State start, int maxSteps, double gamma)
        {
                next_s.CopyFrom(start);
                int i = 0;
                int prevRows = 0;
                score = 0;

                while (!next_s.hasLost() && i < maxSteps)
                {
                        next_s.makeMove(chooseAction(next_s, next_s.legalMoves()));
                        score += gamma * (next_s.getRowsCleared() - prevRows);
                        prevRows = next_s.getRowsCleared();
                        lines = prevRows;
                        gamma *= gamma;
                        i++;
                }

                return next_s;
        }
        


        
        public int chooseAction(State s, int[][] legalMoves)
        {
                double min_loss = Double.MAX_VALUE;
                int min_idx = -1;
                //int depth = 0;
                prev_s.CopyFrom(s);;
                for (int i = 0; i < legalMoves.length; i++)
                {
                        test_s.CopyFrom(s);
                        double loss = 0;
                           
                        
                        if (!test_s.makeMove(i))
                        {
                                loss = Double.MAX_VALUE;
                        }
                        else
                        {
                                loss = getLoss(prev_s, test_s);
                        }
                        
                        //loss = computeAverageLoss(new State(test_s), i, depth); 

                        if (loss < min_loss)
                        {
                                min_loss = loss;
                                min_idx = i;
                        }
                }
                if (min_idx == -1)
                        min_idx = (int) (Math.random() * legalMoves.length);
                // return random action
                // System.out.printf("%d\n", legalMoves.length);
                return min_idx;
        }

        public double getLoss(State start, State s) 
        {
                double loss = 0.0;
                
                if(s.lost)
                {
                        return Double.MAX_VALUE;
                }

                loss += theta[feature.OVERHANG.ordinal()] * getFeatureOverhang(s);

                loss += theta[feature.LINES_CLEARED.ordinal()] * (start.getRowsCleared() - s.getRowsCleared()); 

                loss += theta[feature.MAX_HEIGHT.ordinal()] * getFeatureMaxHeight(s);

                loss += theta[feature.MIN_HEIGHT.ordinal()] * getFeatureMinHeight(s);

                loss += theta[feature.NUM_SQUARES.ordinal()] * getFeatureNumSquares(s);

                loss += theta[feature.DIFFS.ordinal()] * getFeatureDiffs(s);

                loss += theta[feature.ISOLATION.ordinal()] * getFeatureIsolation(s);
                
                loss += theta[feature.HOLES.ordinal()] * getFeatureHoles(s);
                return loss;
        }
        
        public double getFeatureHoles(State s)
        {
                int field[][] = s.getField();
                int count_holes = 0;
                for (int c = 0; c < State.COLS; c++)
                {
                        if(field[0][c] == 0)
                        {
                                count_holes++;
                        }
                        
                        for (int r = 1; r < State.ROWS - 1; r++)
                        {
                             if(field[r][c] != 0)
                             {
                                     continue;
                             }
                             else
                             {
                                     if(field[r + 1][c] != 0 && field[r - 1][c] != 0)
                                     {
                                             count_holes++;
                                     }
                             }
                        }
                        
                }
                
                return count_holes;
        }

        public double getFeatureOverhang(State s)
        {
                int field[][] = s.getField();
                int top[] = s.getTop();
                int count_overhang = 0;
                for (int c = 0; c < State.COLS; c++)
                {
                		
                        for (int r = top[c] - 1; r >= 0; r--)
                        {
                                if (field[r][c] == 0)
                                        count_overhang++;
                        }
                }
                return count_overhang;
        }

        public double getFeatureSumHeight(State s)
        {
                int sum_height = 0;
                int top[] = s.getTop();
                for (int c = 0; c < State.COLS; c++)
                {
                        sum_height += top[c];
                }
                return sum_height;
        }

        public double getFeatureMaxHeight(State s)
        {
                int max_height = 0;
                int top[] = s.getTop();
                for (int i = 0; i < top.length; i++)
                {
                        if (top[i] > max_height)
                                max_height = top[i];
                }
                return max_height;
        }

        public double getFeatureMinHeight(State s)
        {
                int min_height = State.ROWS + 1;
                int top[] = s.getTop();
                for (int i = 0; i < top.length; i++)
                {
                        if (top[i] < min_height)
                                min_height = top[i];
                }
                return min_height;
        }

        public double getFeatureNumSquares(State s)
        {
                int count_squares = 0;
                int field[][] = s.getField();

                for (int r = 0; r < State.ROWS; r++)
                {
                        for (int c = 0; c < State.COLS; c++)
                        {
                                if (field[r][c] > 0)
                                {
                                        count_squares++;
                                }
                        }

                }
                return count_squares;
        }

        public double getFeatureDiffs(State s)
        {
                int top[] = s.getTop();
                int diff = 0;
                for (int c = 0; c < State.COLS - 1; c++)
                {
                        diff += Math.abs(top[c] - top[c + 1]);
                }
                return diff;
        }

        public double getFeatureIsolation(State s)
        {
                int count_isolation = 0;
                int top[] = s.getTop();
                int field[][] = s.getField();

                for (int c = 1; c < State.COLS - 1; c++)
                {
                        for (int r = 0; r < top[c]; r++)
                        {
                                // Find one occupied square
                                if (field[r][c] > 0)
                                {
                                        if (field[r][c + 1] == 0)
                                                count_isolation++;
                                        if (field[r][c - 1] == 0)
                                                count_isolation++;
                                }
                        }
                }
                return count_isolation;
        }

        @Override
        public int compareTo(Agent arg0)
        {
                if(arg0 == this)
                {
                        return 0;
                }
                
                if(arg0.score < score)
                {
                        return -1;
                }
                else if(arg0.score > score)
                {
                        return 1;
                }
                else
                {
                        return 0;
                }
        }
}
